# -*- coding: utf-8 -*-
import logging
import sys

from selenium import webdriver
from utils.ClipboardUtil import Clipboard
from utils.KeyBoardUtil import KeyBoardKeys
from utils.ele_object import *
from utils.WaitUntil import WaitUntil
from utils.DirAndTime import *
from utils.RandomUtil import RandomUtil
from utils.mysql_select import MysqlSelect
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.select import Select

driver = None
wait_util = None
driver: webdriver.Chrome
wait_util: WaitUntil(driver)

def get_element(driver, by: str, locator: str, timeout: int = 10):
    """
    根据定位方式查找 Web 元素（支持显式等待）

    参数说明：
        driver: WebDriver 实例 - 浏览器驱动对象
        by: 定位方式（字符串） - 支持的定位类型见下方by_mapping字典
        locator: 定位表达式（字符串） - 具体定位字符串（如ID值、XPath表达式等）
        timeout: 等待超时时间（整数，秒） - 默认10秒等待时间

    返回值：
        WebElement对象 - 找到的页面元素

    可能抛出的异常：
        ValueError: 当传入不支持的定位方式时
        TimeoutException: 当元素在指定时间内未找到时

    使用示例：
        # 查找ID为"username"的元素（最多等待10秒）
        username = get_element(driver, "id", "username")

        # 查找XPath指定的元素（自定义等待5秒）
        submit_btn = get_element(driver, "xpath", "//button[@type='submit']", 5)
    """
    # 定位方式映射字典
    by_mapping = {
        "id": By.ID,
        "name": By.NAME,
        "class": By.CLASS_NAME,
        "xpath": By.XPATH,
        "link_text": By.LINK_TEXT,
        "partial_link_text": By.PARTIAL_LINK_TEXT,
        "tag_name": By.TAG_NAME,
        "css_selector": By.CSS_SELECTOR,
    }

    # 验证定位方式是否支持
    if by not in by_mapping:
        # 如果传入的定位方式不在映射字典中，抛出包含可用选项的详细错误信息
        raise ValueError(
            f"不支持的定位方式: '{by}'. "
            f"可用的定位方式有: {list(by_mapping.keys())}"
        )

    # 使用WebDriverWait进行显式等待
    return WebDriverWait(driver, timeout).until(
        EC.presence_of_element_located((by_mapping[by], locator))
    )


# 打开浏览器
def open_browser(browser):
    global driver, wait_util
    try:
        if browser.lower() == 'ie':
            driver = webdriver.Ie()
        elif browser.lower() == 'chrome':
            driver = webdriver.Chrome()
        elif browser.lower() == 'edge':
            driver = webdriver.Edge()
        else:
            driver = webdriver.Firefox()
    except Exception as e:
        raise e
    else:
        wait_util = WaitUntil(driver)  # driver 创建之后， 创建等待类实例对象


# 浏览器窗口最大化
def maximize_browser():
    try:
        driver.maximize_window()
    except Exception as e:
        raise e


# 加载网址
def load_url(url):
    """
    :param url: 地址
    :return:
    """
    try:
        driver.get(url)
    except Exception as e:
        raise e


# 强制等待
def sleep(sleep_seconds):
    try:
        import time
        time.sleep(sleep_seconds)
    except Exception as e:
        raise e


# 清空输入的内容
def clear(by, locator):
    try:
        get_element(driver, by, locator).clear()
    except Exception as e:
        raise e


# 输入框中输入内容
def input_value(by, locator, value):
    try:
        element = get_element(driver, by, locator)
        # element.click()
        element.send_keys(value)
    except Exception as e:
        raise e

def execute_script():
    import time
    # 方法一：坐标法
    driver.execute_script('window.scrollTo(0,500)')   # 横坐标不变，纵坐标 滚动到1000像素点
    time.sleep(2)   # 等待一段时间，方便查看滚动的效果


def input_random_str_value(by, locator, value, length):
    """
    输入拼接上的随机字符串的内容
    :param by: 定位方式
    :param locator: 定位元素值
    :param value: 输入内容
    :param length: 拼接随机字符串长度
    :return:
    """
    random_value = RandomUtil.generate_random_string(length)
    value = value + random_value
    try:
        element = get_element(driver, by, locator)
        # element.click()
        element.send_keys(value)
    except Exception as e:
        raise e


def input_random_int_value(by, locator, value, start, end):
    """
    拼接上的范围内的数字
    :param by: 定位方式
    :param locator: 定位元素值
    :param value: 输入内容
    :param start: 开始范围
    :param end: 结束范围
    :return:
    """
    random_value = RandomUtil.generate_random_number(start, end)
    value = value + random_value
    try:
        element = get_element(driver, by, locator)
        # element.click()
        element.send_keys(value)
    except Exception as e:
        raise e


# 点击操作
def click_btn(by, locator):
    try:
        get_element(driver, by, locator).click()
    except Exception as e:
        raise e


# 断言页面的title
def assert_title(title_str):
    try:
        assert title_str in driver.title, "%s not found in title!" % title_str
    except AssertionError as e:
        raise AssertionError(e)
    except Exception as e:
        raise e


# 断言目标字符串是否包含在页面源码中
def assert_string_in_page_source(assert_string):
    try:
        assert assert_string in driver.page_source, "%s not found in page source!" % assert_string
    except AssertionError as e:
        raise AssertionError(e)
    except Exception as e:
        raise e


# 获取当前页面的title
def get_title():
    try:
        return driver.title
    except Exception as e:
        raise e


# 获取页面源码
def get_page_source():
    try:
        return driver.page_source
    except Exception as e:
        raise e


# 切换到frame里面
def switch_to_frame(by, locator):
    try:
        driver.switch_to.frame(get_element(driver, by, locator))
    except Exception as e:
        raise e


# 跳到默认的frame
def switch_to_default():
    try:
        driver.switch_to.default_content()
    except Exception as e:
        raise e

# 模拟ctrl+c键
def ctrl_c():
    try:
        KeyBoardKeys.two_keys('ctrl', 'c')
        sleep(2)
    except Exception as e:
        raise e


# 模拟ctrl+v键
def ctrl_v(value):
    try:
        Clipboard.set_text(value)
        sleep(2)
        KeyBoardKeys.two_keys('ctrl', 'v')
    except Exception as e:
        raise e

# 模拟ctrl+x键
def ctrl_x():
    try:
        KeyBoardKeys.two_keys('ctrl', 'x')
        sleep(2)
    except Exception as e:
        raise e

# 模拟ctrl+a键
def ctrl_a():
    try:
        KeyBoardKeys.two_keys('ctrl', 'a')
        sleep(2)
    except Exception as e:
        raise e

# 模拟tab键
def tab_key():
    try:
        KeyBoardKeys.one_key('tab')
    except Exception as e:
        raise e


# 模拟enter键
def enter_key():
    try:
        KeyBoardKeys.one_key('enter')
    except Exception as e:
        raise e


# 屏幕截图
def save_screen_shot():
    picture_name = DirAndTime.create_picture_path() + '\\' + DirAndTime.get_current_time() + '.png'
    try:
        driver.get_screenshot_as_file(picture_name)
    except Exception as e:
        raise e
    else:
        return picture_name

# 鼠标悬停
def hover_element(by, locator):
    try:
        element = get_element(driver, by, locator)
        ActionChains(driver).move_to_element(element).perform()
    except Exception as e:
        raise e

# 鼠标双击
def double_click(by, locator):
    try:
        element = get_element(driver, by, locator)
        ActionChains(driver).double_click(element).perform()
    except Exception as e:
        raise e

# 根据索引选择下拉框选项
def select_by_index(by, locator, index):
    try:
        element = get_element(driver, by, locator)
        Select(element).select_by_index(index)
    except Exception as e:
        raise e

# 根据文本选择下拉框选项
def select_by_text(by, locator, text):
    try:
        element = get_element(driver, by, locator)
        Select(element).select_by_visible_text(text)
    except Exception as e:
        raise e

# 弹窗确认
def accept_alert():
    try:
        driver.switch_to.alert.accept()
    except Exception as e:
        raise e

# 弹窗取消
def dismiss_alert():
    try:
        driver.switch_to.alert.dismiss()
    except Exception as e:
        raise e

# 获取弹窗文本
def get_alert_text():
    try:
        return driver.switch_to.alert.text
    except Exception as e:
        raise e

# 浏览器后退
def go_back():
    try:
        driver.back()
    except Exception as e:
        raise e

# 浏览器前进
def go_forward():
    try:
        driver.forward()
    except Exception as e:
        raise e


def wait_presence_of_element_located(by, locator):
    """
    显示等待页面元素出现在DOM中，不一定可见
    :param by:
    :param locator:
    :return:
    """
    wait_util.presence_of_element_located(by, locator)


def wait_frame_to_be_available_and_switch_to_it(by, locator):
    """
    检查frame是否存在，存在就切换到frame中
    :param by:
    :param locator:
    :return:
    """
    wait_util.frame_to_be_available_and_switch_to_it(by, locator)


def wait_visibility_of_element_located(by, locator):
    """
    显示等待页面元素出现在DOM中，并且可见
    :param by:
    :param locator:
    :return:
    """
    wait_util.visibility_of_element_located(by, locator)


# 关闭浏览器
def quit_browser():
    global driver
    try:
        if driver is not None:
            driver.quit()
            driver = None
    except Exception as e:
        raise e


def assert_mysql_first_value(table, key_data=None, value_data=None, param=None, assert_value=None):
    """
    断言单条数据, 获取对应的字段的值
    :param table: 表名
    :param key_data: 判断的字段名用[]传入，[字段1,字段2,字段3]
    :param value_data: 判断的字段值用[]传入，[值1,值2,值3]
    :param param: 你所要获取到字段的条件
    :param assert_value: 要断言的值
    :return:
    """
    if key_data:
        # key_data = [f'{key_data}']
        key_data = key_data

    if value_data:
        # value_data = [f'{value_data}']
        value_data = value_data
    result = MysqlSelect().mysql_select_first(table, key_data, value_data, param)
    assert assert_value == result, f'预期结果是：{assert_value},实际结果是：{result},断言不通过'
    logging.info(f'预期结果是：{assert_value},实际结果是：{result},断言通过')


def assert_mysql_length(table, key_data=None, value_data=None, assert_value=None):
    """
    断言获取到符合条件数据的数量
    :param table: 表名
    :param key_data: 判断的字段名用[]传入，[字段1,字段2,字段3]
    :param value_data: 判断的字段值用[]传入，[值1,值2,值3]
    :param assert_value: 预期长度
    :return:
    """
    if key_data:
        # key_data = [f'{key_data}']
        key_data = key_data

    if value_data:
        # value_data = [f'{value_data}']
        value_data = value_data

    result = MysqlSelect().mysql_length(table, key_data, value_data)
    assert assert_value == result, f'预期存在数据数量：{assert_value},实际存在数据数量：{result},断言不通过'
    logging.info(f'预期存在数据数量：{assert_value},实际存在数据数量：{result},断言通过')


def execute_action(action_name, *args):
    """
    使用反射机制执行页面动作
    :param action_name: 要执行的动作名称（函数名）
    :param args: 函数参数
    :return: 函数执行结果
    """
    # 获取当前模块中所有函数
    current_module = sys.modules[__name__]

    # 检查动作是否存在
    if not hasattr(current_module, action_name):
        raise ValueError(f"未知的动作: '{action_name}'。可用动作: {_get_available_actions()}")

    # 获取动作函数
    action_func = getattr(current_module, action_name)

    # 执行动作
    return action_func(*args)


def _get_available_actions():
    """获取所有可用的动作列表（用于错误提示）"""
    current_module = sys.modules[__name__]
    # 筛选出所有可调用的函数（过滤掉私有函数和特殊方法）
    return [name for name, obj in vars(current_module).items()
            if callable(obj) and not name.startswith('_')]

if __name__ == '__main__':

    from time import sleep
    assert_mysql_first_value('area', ["name", "sort"], ['上地', '2'], 'atype', 2)
    assert_mysql_length('area', ["name", "sort"], ["上地", '2'], 1)
